package jmine.tec.rpc.client.comm;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.text.ParseException;

import jmine.tec.proxy.chain.ChainElement;
import jmine.tec.proxy.chain.InvocationChain;
import jmine.tec.rpc.RPCMessages;
import jmine.tec.rpc.common.RPCException;
import jmine.tec.rpc.common.request.RestRequest;
import jmine.tec.rpc.common.request.RestResponse;
import jmine.tec.rpc.common.serialization.Serializer;
import jmine.tec.utils.ReflectiveMethodUtils;
import jmine.tec.utils.collection.MultiMap;
import jmine.tec.utils.collection.impl.EmptyMultiMap;
import jmine.tec.utils.io.http.Connector;
import jmine.tec.utils.io.http.HttpMethod;
import jmine.tec.utils.io.http.exception.HttpException;

/**
 * Chain element que intercepta chamadas a um serviço, despachando para um receptor remoto.
 * 
 * @author lundberg
 */
public class ClientStubChainElement implements ChainElement {

    private final Connector connector;

    private final Serializer serializer;

    private final String path;

    /**
     * Construtor
     * 
     * @param connector connector
     * @param serializer serializer
     * @param path path
     */
    public ClientStubChainElement(Connector connector, Serializer serializer, String path) {
        this.connector = connector;
        this.serializer = serializer;
        this.path = path;
    }

    /**
     * {@inheritDoc}
     */
    public Object aroundInvoke(InvocationChain chain) throws Throwable {
        final Method method = chain.getInvokingMethod();
        if (ReflectiveMethodUtils.isEqualsMethod(method)) {
            return chain.getProxyObject() == chain.getParams()[0];
        } else if (ReflectiveMethodUtils.isHashCodeMethod(method)) {
            return System.identityHashCode(chain.getProxyObject());
        } else if (ReflectiveMethodUtils.isFinalizeMethod(method)) {
            this.connector.close();
            return null;
        } else if (ReflectiveMethodUtils.isToStringMethod(method)) {
            return "[RPC Proxy path= " + this.path + "]";
        }
        RestResponse response;
        try {
            response = this.unpack(this.dispatch(chain));
        } catch (HttpException e) {
            throw new RPCException(RPCMessages.COMMUNICATION_ERROR.create(e.getMessage()), e);
        } catch (IOException e) {
            throw new RPCException(RPCMessages.COMMUNICATION_ERROR.create(e.getMessage()), e);
        }
        if (response.exceptionThrown()) {
            throw response.getException();
        } else {
            return response.getResult();
        }
    }

    /**
     * Deserializa a resposta vinda do servidor.
     * 
     * @param answer answer
     * @return object
     * @throws ParseException ParseException
     * @throws IOException IOException
     */
    private RestResponse unpack(InputStream answer) throws IOException, ParseException {
        return (RestResponse) this.serializer.deserialize(answer);
    }

    /**
     * Despacha a cadeia remotamente
     * 
     * @param chain chain
     * @return InputStream
     * @throws HttpException HttpException
     * @throws IOException IOException
     */
    private InputStream dispatch(InvocationChain chain) throws HttpException, IOException {
        InputStream body = this.serializer.serialize(this.createServiceRequest(chain));
        MultiMap<String, String> query = new EmptyMultiMap<String, String>();
        return this.connector.makeRequestWithBody(this.path, HttpMethod.POST, null, query, body).head();
    }

    /**
     * Cria o ServiceRequest para o chain
     * 
     * @param chain chain
     * @return ServiceRequest
     */
    private RestRequest createServiceRequest(InvocationChain chain) {
        String methodName = chain.getInvokingMethod().getName();
        Class<?>[] parameterTypes = chain.getInvokingMethod().getParameterTypes();
        Object[] parameters = chain.getParams();
        return new RestRequest(methodName, parameterTypes, parameters);
    }
}
